package com.xianyu.open.restclient.config;

import com.xianyu.open.restclient.annotation.EnableRestClient;
import com.xianyu.open.restclient.annotation.RestClient;
import com.xianyu.open.restclient.constants.RestClientGlobalConstants;
import com.xianyu.open.restclient.constants.TypeAndMethodMap;
import com.xianyu.open.restclient.factory.ProxyCreatorFactoryBean;
import com.xianyu.open.restclient.factory.ProxyFactoryBean;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionReaderUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.env.Environment;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.http.HttpMethod;
import org.springframework.util.ClassUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;

/**
 *
 * @author bailinan
 * @date 2018/6/16
 */
public class RestClientsRegistrar implements ImportBeanDefinitionRegistrar,
        ResourceLoaderAware, EnvironmentAware {


    private ResourceLoader resourceLoader;

    private Environment environment;

    public RestClientsRegistrar() {

    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) {
        try {
            registerRestClients(annotationMetadata, registry);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 注册bean
     * @param annotationMetadata
     * @param registry
     * @throws ClassNotFoundException
     */
    private void registerRestClients(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry registry) throws ClassNotFoundException {
        // 过滤
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        Set<String> basePackages = getBasePackages(annotationMetadata);
        scanner.setResourceLoader(this.resourceLoader);
        scanner.addIncludeFilter(new AnnotationTypeFilter(RestClient.class));

        for (String basePackage : basePackages) {
            Set<BeanDefinition> candidateComponents = scanner
                    .findCandidateComponents(basePackage);
            for (BeanDefinition beanDefinition : candidateComponents) {
                AnnotatedBeanDefinition annotatedBeanDefinition = (AnnotatedBeanDefinition) beanDefinition;
                AnnotationMetadata metadata = annotatedBeanDefinition.getMetadata();
                if (!metadata.isInterface()) {
                    throw new RuntimeException();
                }
                registerRestClient(registry, metadata);
            }
        }
    }

    private void registerRestClient(BeanDefinitionRegistry registry, AnnotationMetadata metadata) throws ClassNotFoundException {
        String className = metadata.getClassName();
        Class<?> aClass = Class.forName(className);
        String beanName = className.substring(className.lastIndexOf("\\.") + 1);

        setServerInfo(aClass);
        setMethodInfo(aClass);

        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(ProxyFactoryBean.class);
        beanDefinitionBuilder.addPropertyValue("type", className);
        beanDefinitionBuilder.addPropertyValue("proxyCreator", ProxyCreatorFactoryBean.getProxyCreator());
        BeanDefinitionHolder beanDefinitionHolder = new BeanDefinitionHolder(beanDefinitionBuilder.getBeanDefinition(), beanName);
        BeanDefinitionReaderUtils.registerBeanDefinition(beanDefinitionHolder, registry);
    }

    /**
     * 设置method
     * @param type
     */
    private void setMethodInfo(Class<?> type) {
        String className = type.getCanonicalName();
        Map<String, MethodInfo> methodInfoMap = new HashMap<>();
        Method[] methods = type.getMethods();
        for (Method method : methods) {
            MethodInfo methodInfo = new MethodInfo();
            setMethodUrlAndHttpMethodInfo(methodInfo, method);
            methodInfoMap.put(method.toString(), methodInfo);
        }
        TypeAndMethodMap.typeAndMethodInfo.put(className, methodInfoMap);
    }

    private void setMethodUrlAndHttpMethodInfo(MethodInfo methodInfo, Method method) {
        Annotation annotation = method.getAnnotations()[0];
        if (annotation instanceof GetMapping) {
            methodInfo.setHttpMethod(HttpMethod.GET);
            GetMapping relMethodAnno = (GetMapping) annotation;
            methodInfo.setUrl(relMethodAnno.value()[0]);
        } else if (annotation instanceof PostMapping) {
            methodInfo.setHttpMethod(HttpMethod.POST);
            PostMapping relMethodAnno = (PostMapping) annotation;
            methodInfo.setUrl(relMethodAnno.value()[0]);
        } else if (annotation instanceof PutMapping) {
            methodInfo.setHttpMethod(HttpMethod.PUT);
            PutMapping relMethodAnno = (PutMapping) annotation;
            methodInfo.setUrl(relMethodAnno.value()[0]);
        } else if (annotation instanceof DeleteMapping) {
            methodInfo.setHttpMethod(HttpMethod.DELETE);
            DeleteMapping relMethodAnno = (DeleteMapping) annotation;
            methodInfo.setUrl(relMethodAnno.value()[0]);
        }
        String url = methodInfo.getUrl();

        // 解析接口地址
        methodInfo.setUrl(getElValue(url));
        methodInfo.setRelUrl(methodInfo.getUrl());
    }


    /**
     * 设置server
     * @param type
     */
    private void setServerInfo(Class<?> type) {
        RestClient annotation = type.getAnnotation(RestClient.class);
        String value = annotation.value();
        // 解析服务地址
        TypeAndMethodMap.typeAndServerInfo.put(type.getCanonicalName(),
                new ServerInfo(getElValue(value)));
    }


    /**
     * 定义简单的过滤规则
     * @return
     */
    private ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                return beanDefinition.getMetadata().isIndependent();
            }
        };
    }

    /**
     * 获取basePackege
     * @param annotationMetadata
     * @return
     */
    private Set<String> getBasePackages(AnnotationMetadata annotationMetadata) {
        Map<String, Object> attributes = annotationMetadata
                .getAnnotationAttributes(EnableRestClient.class.getCanonicalName());

        Set<String> basePackages = new HashSet<>();
        for (String pkg : (String[]) attributes.get("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }

        if (basePackages.isEmpty()) {
            basePackages.add(
                    ClassUtils.getPackageName(annotationMetadata.getClassName()));
        }
        return basePackages;
    }

    /**
     * 获取EL表达式对应的环境配置信息
     * @param value
     * @return
     */
    private String getElValue(String value) {
        Matcher matcher = RestClientGlobalConstants.CONFIG_RULE.matcher(value);
        StringBuffer buffer = new StringBuffer();
        while (matcher.find()){
            String newValue = this.environment.getProperty(matcher.group(1));
            matcher.appendReplacement(buffer, newValue);
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }
}
